﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SimpleActor.Test
{
    [Flags]
    public enum SchedulingTestMode
    {
        None = 0x00,
        PostOnStop = 0x01,
        SendOnStop = 0x02,
    };

    public class CountInfo: IEquatable<CountInfo>
    {
        private readonly int externalCount;
        private readonly int internalCount;

        public CountInfo(int externalCount, int internalCount)
        {
            this.externalCount = externalCount;
            this.internalCount = internalCount;
       }

        public override int GetHashCode()
        {
            var t = Tuple.Create(externalCount, internalCount);
            var ret = t.GetHashCode();
            return ret;
        }

        public override bool Equals(object obj)
        {
            var other = obj as CountInfo;
            var ret = Equals(other);
            return ret;
        }

        public bool Equals(CountInfo other)
        {
            var ret = other != null
                   && this.externalCount == other.externalCount
                   && this.internalCount == other.internalCount;
            return ret;
        }

        public override string ToString()
        {
            var ret = string.Format("external={0}, internal={1}", externalCount, internalCount);
            return ret;
        }
    }

    public interface ISchedulingTest
    {
        Task<CountInfo> GetCountInfo();

        Task<CountInfo> DoSend(int count);

        Task<CountInfo> DoPost(int count);
    }

    public interface ISchedulingTestService : ISchedulingTest, IActor
    {
    }

    internal interface ISchedulingTestServant : ISchedulingTest, IActorServant
    {
    }

    public static class SchedulingTestServiceFactory
    {
        public static ISchedulingTestService Create()
        {
            var servant = new SchedulingTestServant();
            var scheduler = new MessageScheduler<ISchedulingTestServant>("Service", servant);
            var proxy = new SchedulingTestServiceProxy(scheduler);
            return proxy;
        }
    }

    internal class SchedulingTestServiceProxy : ActorProxyBase<ISchedulingTestServant>, ISchedulingTestService
    {
        public SchedulingTestServiceProxy(IMessageScheduler<ISchedulingTestServant> scheduler)
            : base(scheduler)
        {
        }

        public Task<CountInfo> GetCountInfo()
        {
            var func = Scheduler.GetQueueFunc<CountInfo>(s => s.GetCountInfo());
            var ret = func();
            return ret;
        }

        public Task<CountInfo> DoSend(int count)
        {
            var func = Scheduler.GetQueueFunc<int, CountInfo>((s, p) => s.DoSend(p));
            var ret = func(count);
            return ret;
        }

        public Task<CountInfo> DoPost(int count)
        {
            var func = Scheduler.GetQueueFunc<int, CountInfo>((s, p) => s.DoPost(p));
            var ret = func(count);
            return ret;
        }
    }

    internal class SchedulingTestServant : ActorServantBase, ISchedulingTestServant
    {
        private readonly SchedulingTestMode mode;
        private int externalCount;
        private int internalCount;

        public SchedulingTestServant()
            : this(SchedulingTestMode.None)
        {
        }

        public SchedulingTestServant(SchedulingTestMode mode)
        {
            this.mode = mode;
            this.externalCount = 0;
            this.internalCount = 0;
        }

        public override void OnStart()
        {
            base.OnStart();
        }

        public override void OnStop()
        {
            base.OnStop();

            var context = SynchronizationContext.Current;

            if (mode.HasFlag(SchedulingTestMode.PostOnStop))
            {
                DoPostCore(2);
            }
            if (mode.HasFlag(SchedulingTestMode.SendOnStop))
            {
                DoSendCore(2);
            }
        }

        public override void Run()
        {
            var timeout = Timeout.InfiniteTimeSpan;

            var s = Handler.TryHandleMessage(timeout);
            while(s.CanNext())
            {
                if (s == MessageHandlingStatus.Handled)
                {
                    externalCount++;
                }
                s = Handler.TryHandleMessage(timeout);
            }
        }

        private void DoInternally()
        {
            internalCount++;
        }

        private CountInfo GetCountInfoCore()
        {
            var ret = new CountInfo(externalCount, internalCount);
            return ret;
        }

        public Task<CountInfo> GetCountInfo()
        {
            var ret = Task.FromResult(GetCountInfoCore());
            return ret;
        }

        private void DoSendCore(int count)
        {
            Debug.WriteLine("DoSendCore:{0} / {1}", count, GetCountInfoCore());

            if (count > 0)
            {
                DoInternally();

                var context = SynchronizationContext.Current;
                context.Send((_) => DoSendCore(count - 1), null);
            }
        }

        public Task<CountInfo> DoSend(int count)
        {
            Debug.WriteLine("[Enter]DoSend:{0} / {1}", count, GetCountInfoCore());
            
            var context = SynchronizationContext.Current;
            context.Send((_) => DoSendCore(count), null);

            var ret = GetCountInfo();


            Debug.WriteLine("[Exit]DoSend:{0} / {1}", count, GetCountInfoCore());
            return ret;
        }

        private void DoPostCore(int count)
        {
            Debug.WriteLine("DoPostCore:{0} / {1}", count, GetCountInfoCore());

            if (count > 0)
            {
                DoInternally();

                var context = SynchronizationContext.Current;
                context.Post((_) => DoPostCore(count - 1), null);
            }
        }

        public Task<CountInfo> DoPost(int count)
        {
            Debug.WriteLine("[Enter]DoPost:{0} / {1}", count, GetCountInfoCore());

            var context = SynchronizationContext.Current;
            context.Post((_) => DoPostCore(count), null);

            var ret = GetCountInfo();

            Debug.WriteLine("[Exit]DoPost:{0} / {1}", count, GetCountInfoCore());

            return ret;
        }
    }
}
